// Copyright 2012 The Glue BSF Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('glue.Controller')

goog.require('glue.iController');
goog.require('glue.Model');

goog.require('glue.Event');
goog.require('glue.EventType');
goog.require('glue.SpecController')
goog.require('glue.View');
goog.require('glue.Error');


goog.require('goog.History');
goog.require('goog.dom');
goog.require('goog.dom.classes');
goog.require('goog.events.EventHandler');
goog.require('goog.object')
goog.require('goog.Uri');
goog.require('goog.ui.Css3ButtonRenderer');
goog.require('goog.ui.decorate');
goog.require('goog.ui.Zippy');
goog.require('goog.structs.Map');
goog.require('goog.style');

/** 
 * Controls the interactions between the Views and Model.  
 * It determines how user interactions change which view is loaded 
 * and cleans up the transitions between views.
 * 
 * @param {glue.Model} model
 * @param {Object=} opt_options
 * 
 * @export
 * @constructor
 */
glue.Controller = function(model,opt_options){
	this.options_ = {
		main:'main',
		history:'historyInput',
		historyIframe:'historyIframe',
		defaultView:'home',
		hideHelper:'hidden',
		zipClasses:{
			header:'ui-no-swish ui-widget ui-widget-header ui-corner-all',
			button:'kp-text-button',
			container:'ui-widget-content ui-corner-all',			
			toggleHelper:'ui-bl-swish'
		},
		spec:glue.SpecController
	};
	if(goog.isDefAndNotNull(opt_options)){
		goog.object.extend(this.options_,opt_options);
	}
	
	this.eh_ = new goog.events.EventHandler(this);
	
	var historyInput = goog.dom.getElement(this.options_.history);
	if(!goog.isDefAndNotNull(historyInput)){
		historyInput = goog.dom.createDom('input', {'type':'text','class':this.options_.hideHelper});
		goog.dom.appendChild(document.body, historyInput);	
	}
	var iframe = goog.dom.getElement(this.options_.historyIframe);
	
	this.history_ = new goog.History(false,null,historyInput,iframe);
	
	this.eh_.listen(this.history_, goog.history.EventType.NAVIGATE, goog.bind(this.navigationHandler_,this));	
	
	if(goog.isFunction(this.options_.spec.create)){
		this.iController_ = this.options_.spec.create(model,this);	
	}else{
		this.iController_ = new this.options_.spec(model,this);
	}
	
	
	
	this.views_ = new goog.structs.Map();
	this.addView(glue.Error);
	
	this.main_ = goog.dom.getElement(this.options_.main);
	goog.dom.removeChildren(this.main_);
}

/**
 * Initializes the Controller
 * 
 * @export
 * @public
 */
glue.Controller.prototype.initialize = function(){
	this.history_.setEnabled(true);
	
}

/**
 * Handles goog.history.EventType.NAVIGATE events.
 * 
 * @param {goog.events.EventType} e
 * 
 * @private
 * @param 
 */
glue.Controller.prototype.navigationHandler_ = function(e){
	
	var queryData = new goog.Uri.QueryData(e.token)	
	
	if(!queryData.containsKey('type')){
		queryData.set('type',this.options_.defaultView);
	}
	
	this.load(queryData);
}

/**
 * Adds a widget to the Application. Widgets are permanent Views places at specific areas on the screen.
 * 
 * @export
 * @public
 * 
 * @param {glue.View} widget
 * @param {glue.iController.Location} position
 * @param= {boolean} opt_prepend defaults to false
 */
glue.Controller.prototype.addWidget = function(widget,position,opt_prepend){
	opt_prepend = opt_prepend || false;
	
	var w = new widget();	
	w.setController(this.iController_);
	
	var container = goog.dom.createDom('div',{'class':'glue-widget-'+position.id});	
	
	var inner = container;
	if(position.zip){
		goog.dom.classes.add(container,'kp-zippy '+this.options_.zipClasses.container);		
		var name = w.ViewName;		
		var holder = goog.dom.createDom('div'); 
		var header = goog.dom.createDom('h4',{'class':this.options_.zipClasses.header,'style':'width:85%;text-align:center'},name)
		var button = goog.dom.createDom('div',{'class':'goog-css3-button '+this.options_.zipClasses.button,'style':'float:right;'},'-');
		goog.dom.append(holder,button);
		goog.dom.append(holder,header);
		goog.ui.decorate(button);
		
		inner = goog.dom.createDom('div');
		goog.dom.append(container,holder);
		goog.dom.append(container,inner);
		var zippy = new goog.ui.Zippy(button,inner,true);
		
		var zippyToggle = function(event) {			
			goog.dom.classes.toggle(header,this.options_.zipClasses.toggleHelper)
	      if(event.expanded){	      	
	      	event.currentTarget.elHeader_.innerHTML = '-'
	      } else{
	      	event.currentTarget.elHeader_.innerHTML = '+'
	      };	      
	    };
		
		this.eh_.listen(zippy, goog.ui.Zippy.Events.TOGGLE, zippyToggle);
	}else{		
		goog.style.setInlineBlock(container);			
	}
	
	var parent = position.element;
	
	goog.dom.classes.remove(parent,this.options_.hideHelper);
	
	if(opt_prepend){
		goog.dom.insertChildAt(parent,container,0);
	}else{
		goog.dom.append(parent,container);
	}
	w.render(inner)
	
}

/**
 * Disposing the current view and returns to the previous View.
 * 
 * @export
 * @public
 */
glue.Controller.prototype.close = function(){
	history.back();
}

/**
 * Returns the iController saved in this instance.
 * 
 * @export
 * @public
 * 
 * @return {glue.iController}
 */
glue.Controller.prototype.getController = function(){
	return this.iController_;
}

/**
 * Adds a view to application.  A View is a method for representing data.  
 * Views in glue are accessed through an URL hash where type = the view name.
 * 
 * @export
 * @public
 * 
 * @param {glue.View} view View to add to the program.
 */
glue.Controller.prototype.addView = function(view){
	this.views_.set(view.prototype.getViewName(),view);	
}

/**
 * Lists the currently available view names.
 * 
 * @export
 * @public
 * 
 * @return {Array<String>} returns the names of all the loaded views.
 */
glue.Controller.prototype.getViews = function(){
	return this.views_.getKeys()
}

/**
 * Loads a Page Token
 * 
 * @export
 * @public
 * 
 * @param {goog.Uri.QueryData|Object} token Query data for the program to load.
 */
glue.Controller.prototype.load = function(token){
	
	if(!(token instanceof goog.Uri.QueryData)){
		token = goog.Uri.QueryData.createFromMap(token);
	}
	
	if(!goog.isDefAndNotNull(this.params_) || this.params_.toString() !== token.toString()){		
		if(this.history_.getToken() !== token.toString()){		
			this.history_.setToken(token.toString());
		}else{
			this.load_(token)
		}
	}		
}

/**
 * Internal function for handling a load token call.
 * 
 * @private
 * @param {goog.Uri.QueryData|Object} token Query data for the program to load.
 */
glue.Controller.prototype.load_ = function(token){
	
	window.scrollTo(0,0)
	var type = token.get('type','error');
	var view = new (this.views_.get(type,this.views_.get('error')))(token);
	
	view.setController(this.iController_);
	
	if(this.last_ !== undefined){		
		this.last_.dispose();
		if(this.main_.children.length > 0){			
			goog.dom.removeChildren(this.main_);	
		}
	}
	
	view.decorate(this.main_);	
	
	this.last_ = view;	
	this.params_ = token;
	var e = new glue.Event(glue.EventType.NAVIGATE,token,view);			
	this.iController_.dispatchEvent(e);
	
}

/**
 * Refreshes the currently active View and nothing else.  
 * There is no data or JS reload or screen widget refresh.
 * 
 * @export
 * @public
 */
glue.Controller.prototype.refresh = function(){
	this.load_(this.params_)
}

/**
 * Disposes the currently active view and any screen widgets present.
 * 
 * @export
 * @public
 */
glue.Controller.prototype.dispose = function(){
	if(this.last_ !== undefined){
		this.last_.dispose();
	}
	if(this.eh_){
		this.eh_.dispose();
	}
}

